/**
 * Copyright (C) 2010 Olafur Gauti Gudmundsson Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

package com.google.code.jbauble.dao.mapping;

import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * 
 * <p>This is the heart of Morphia and takes care of mapping from/to POJOs/DBObjects<p>
 * <p>This class is thread-safe and keeps various "cached" data which should speed up processing.</p>
 * 
 * @author Olafur Gauti Gudmundsson
 * @author Scott Hernandez
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class Mapper {
	/** The @{@link Id} field name that is stored with mongodb.*/
	public static final String ID_KEY = "id";
	/** Special name that can never be used. Used as default for some fields to indicate default state.*/
	public static final String IGNORED_FIELDNAME = ".";
	/** Special field used by morphia to support various possibly loading issues; will be replaced when discriminators are implemented to support polymorphism*/
	public static final String CLASS_NAME_FIELDNAME = "className";

	/** Set of classes that registered by this mapper */
	private final Map<String, MappedClass> mappedClasses = new ConcurrentHashMap<String, MappedClass>();
	private final Map<String, Set<MappedClass>> mappedClassesByCollection = new ConcurrentHashMap<String, Set<MappedClass>>();
	
	//A general cache of instances of classes; used by MappedClass for EntityListerner(s)
	final Map<Class, Object> instanceCache = new ConcurrentHashMap();
	
	public Mapper() {
	}

	public boolean isMapped(final Class c) {
		return mappedClasses.containsKey(c.getName());
	}

	/** Creates a MappedClass and validates it. */
	public MappedClass addMappedClass(Class c) {
		MappedClass mc = new MappedClass(c, this);
		return addMappedClass(mc, true);
	}
	
	/** Validates MappedClass and adds to internal cache. */
	public MappedClass addMappedClass(MappedClass mc) {
		return addMappedClass(mc, true);
	}
	
	
	/** Add MappedClass to internal cache, possibly validating first. */
	private MappedClass addMappedClass(MappedClass mc, boolean validate) {
		mappedClasses.put(mc.getClazz().getName(), mc);
		
		Set<MappedClass> mcs = mappedClassesByCollection.get(mc.getCollectionName());
		if (mcs == null) {
			mcs = new HashSet();
			mappedClassesByCollection.put(mc.getCollectionName(), mcs);
		}
		mcs.add(mc);

		return mc;
	}

	/** Returns collection of MappedClasses*/
	public Collection<MappedClass> getMappedClasses() {
		return new ArrayList<MappedClass>(mappedClasses.values());
	}

	/** Returns map of MappedClasses by class name */
	public Map<String, MappedClass> getMCMap() {
		return Collections.unmodifiableMap(mappedClasses);
	}

	/**
	 * <p>
	 * Gets the {@link MappedClass} for the object (type). If it isn't mapped,
	 * create a new class and cache it (without validating).
	 * </p>
	 */
	public MappedClass getMappedClass(final Object obj) {
		if (obj == null) {
			return null;
		}
		
		Class type = (obj instanceof Class) ? (Class) obj : obj.getClass();

		MappedClass mc = mappedClasses.get(type.getName());
		if (mc == null) {
			mc = new MappedClass(type, this);
			// no validation
			addMappedClass(mc, false);
		}
		return mc;
	}

	public String getCollectionName(Object object) {
		if (object == null) throw new IllegalArgumentException();
		
		MappedClass mc = getMappedClass(object);
		return mc.getCollectionName();
	}
}
